import _ from '../assets/utils';
import qs from 'qs';//qs被用来将参数对象转换为查询字符串的形式
import { Toast } from 'antd-mobile';//Toast通常用于在页面中显示简短的提示信息，比如请求失败时显示网络错误信息。

/* 核心方法 */
const http = function http(config) {
    if (!_.isPlainObject(config)) config = {};
    // Object.assign() 方法会修改目标对象（第一个参数）。如果目标对象中已有相同属性，则会被覆盖。
    // 它只会拷贝源对象自身的可枚举属性，不会拷贝继承的属性。
    // 如果源对象为 null 或 undefined，则会抛出异常（在严格模式下）。
    config = Object.assign({
        url: '',
        method: 'GET',
        credentials: 'include',
        headers: null,
        body: null,
        params: null,
        responseType: 'json',
        signal: null
    }, config);
    //检查必要的配置项，若没有提供 url，则抛出异常。
    if (!config.url) throw new TypeError('url must be required');
    if (!_.isPlainObject(config.headers)) config.headers = {};
    if (config.params !== null && !_.isPlainObject(config.params)) config.params = null;

    let { url, method, credentials, headers, body, params, responseType, signal } = config;
    //根据配置项中的 params，将参数对象转换为查询字符串，并拼接到 url 中。
    if (params) {
        url += `${url.includes('?') ? '&' : '?'}${qs.stringify(params)}`;
    }
    //如果 body 是一个普通对象，则使用 qs.stringify 方法将其转换为 URL 编码的字符串
    //并设置请求头的 Content-Type 为 'application/x-www-form-urlencoded'。
    if (_.isPlainObject(body)) {
        body = qs.stringify(body);
        headers['Content-Type'] = 'application/x-www-form-urlencoded';
    }

    // 处理Token
    //根据请求的 url 判断是否需要添加 Token，将 Token 添加到请求头的 authorization 字段中。
    let token = _.storage.get('tk'),
        //定义一个数组 safeList，包含了一些安全的请求路径。
        safeList = ['/user_info', '/user_update', '/store', '/store_remove', '/store_list'];
    if (token) {
        //用于匹配 URL 中 /api/ 后的路径部分（不包括查询参数和哈希值）。
        //let path = url.split('/api')[1].split('?')[0].split('#')[0]; // 提取 URL 中 /api/ 后的路径部分
        let reg = /\/api(\/[^?#]+)/,
            [, $1] = reg.exec(url) || [];
        //如果存在 Token 值，并且当前请求的路径在安全列表中，那么将 Token 添加到请求头中，以便进行身份验证。
        let isSafe = safeList.some(item => {
            return $1 === item;
        });
        if (isSafe) headers['authorization'] = token;
    }

    // 将 method 转换为大写形式，同时构建请求的配置对象 config。
    method = method.toUpperCase();
    config = {
        method,
        credentials,
        headers,
        cache: 'no-cache',
        signal
    };
    //如果是 POST、PUT 或 PATCH 请求且存在请求体 body，则将其添加到 config 中。
    if (/^(POST|PUT|PATCH)$/i.test(method) && body) config.body = body;
    //使用 fetch 函数发起请求，并根据结果进行处理和返回。
    return fetch(url, config)
        .then(response => {
            let { status, statusText } = response;
            //通过正则表达式 /^(2|3)\d{2}$/ 测试状态码是否以 2 或 3 开头且后面跟着三位数字，表示成功的 HTTP 状态码。
            if (/^(2|3)\d{2}$/.test(status)) {
                let result;
                switch (responseType.toLowerCase()) {
                    case 'text':
                        result = response.text();//response.text() 方法获取文本数据。
                        break;
                    case 'arraybuffer':
                        result = response.arrayBuffer();//response.arrayBuffer() 方法获取二进制数据。
                        break;
                    case 'blob':
                        result = response.blob();// response.blob() 方法获取 Blob 对象。
                        break;
                    default:
                        result = response.json();//response.json() 方法获取 JSON 格式的数据。
                }
                return result;
            }
            return Promise.reject({
                code: -100,
                status,
                statusText
            });
        })
        .catch(reason => {
            Toast.show({
                icon: 'fail',
                content: '网络繁忙,请稍后再试!'
            });
            return Promise.reject(reason);
        });
};

/* 快捷方法  ------  这是我们在index文件里面调用的方法
遍历 ["GET", "HEAD", "DELETE", "OPTIONS"] 数组，在 http 上定义对应的小写方法名，
这些方法会调用 http 方法并传递相应的配置，然后返回请求结果。*/
["GET", "HEAD", "DELETE", "OPTIONS"].forEach(item => {
    http[item.toLowerCase()] = function (url, config) {
        if (!_.isPlainObject(config)) config = {};
        config['url'] = url;
        config['method'] = item;
        return http(config);
    };
});
["POST", "PUT", "PATCH"].forEach(item => {
    http[item.toLowerCase()] = function (url, body, config) {
        if (!_.isPlainObject(config)) config = {};
        config['url'] = url;
        config['method'] = item;
        config['body'] = body;
        return http(config);
    };
});

export default http;